﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace JackProjectTemplate.Cache.DistributedIdentifyGeneraters.SequentialGuid
{
    public abstract class SequentialGuidGeneratorBase<T> where T : SequentialGuidGeneratorBase<T>
    {
        private static readonly Lazy<T> _lazy =
        new(() => (Activator.CreateInstance(typeof(T), true) as T)!);

        private readonly byte[] _machinePid;
        private int _increment;

        protected SequentialGuidGeneratorBase()
        {
            _increment =
#if NETFRAMEWORK || NETSTANDARD2_0
            // Fall back to the old Random class for compatibility
            new Random().Next
#else
                // Use the RandomNumberGenerator static function where available
                RandomNumberGenerator.GetInt32
#endif
                (500000);
            _machinePid = new byte[5];
#if NET6_0_OR_GREATER
            // For newer frameworks use the preferred static function
            var hash = SHA512.HashData
#else
            // For older frameworks use the old algorithm create function
            using var algorithm = SHA512.Create();
            var hash = algorithm.ComputeHash
#endif
            (Encoding.UTF8.GetBytes(Environment.MachineName));
            for (var i = 0; i < 3; i++)
                _machinePid[i] = hash[i];
            try
            {
                var pid =
#if NET6_0_OR_GREATER
                // For newer frameworks prefer to use the static property on the Environment
                Environment.ProcessId
#else
                    // For older frameworks get the process id the old school way
                    Process.GetCurrentProcess().Id
#endif
                ;
                // use low order two bytes only
                _machinePid[3] = (byte)(pid >> 8);
                _machinePid[4] = (byte)pid;
            }
            catch (SecurityException)
            {
            }
        }

        public static T Instance => _lazy.Value;

        public Guid NewGuid() => NewGuid(DateTime.UtcNow.Ticks);

        public Guid NewGuid(DateTime timestamp)
        {
            var ticks = timestamp.Kind switch
            {
                DateTimeKind.Utc => timestamp.Ticks, // use ticks as is
                DateTimeKind.Local => timestamp.ToUniversalTime().Ticks, // convert to UTC
                _ => throw new ArgumentException("DateTimeKind.Unspecified not supported", nameof(timestamp))
            };

            // run validation after tick conversion
            if (!ticks.IsDateTime())
                throw new ArgumentException("Timestamp must be between January 1st, 1970 UTC and now",
                    nameof(timestamp));

            // Once we've gotten here we have a valid UTC tick count so yield the Guid
            return NewGuid(ticks);
        }

        internal virtual Guid NewGuid(long timestamp)
        {
            // only use low order 3 bytes
            var increment = Interlocked.Increment(ref _increment) & 0x00ffffff;
            return new Guid(
                (int)(timestamp >> 32),
                (short)(timestamp >> 16),
                (short)timestamp,
                [.. _machinePid, .. new[] { (byte)(increment >> 16), (byte)(increment >> 8), (byte)increment }]
            );
        }
    }
}
